//
// Created by ktsq on 2021/8/5.
//

#include "test.h"

#include <kgl.h>

#include <glad/glad.h>
#include <GLFW/glfw3.h>

#include <stb_image.h>

#include "FPSCamera.h"

int ch1_light_main() {
    kgl::Log::Init();
    auto win = &kgl::singleton<kgl::Application>::getInstance()->GetWindow();
    glm::ivec2 winSize = win->GetSize();

    // create shader
    auto shader = kgl::MakeRef<kgl::Shader>("../kgl-data/shader/ch1_light.glsl");

    // create triangle vertices
    float vert[] = {
            // position          //normal           // texCoords
            -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,
            0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f,
            0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f,
            0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f,
            -0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f,
            -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,

            -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
            0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f,
            0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
            0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
            -0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f,
            -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,

            -0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
            -0.5f, 0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
            -0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
            -0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
            -0.5f, -0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
            -0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f,

            0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
            0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
            0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
            0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
            0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
            0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,

            -0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f,
            0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f,
            0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f,
            0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f,
            -0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f,
            -0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f,

            -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,
            0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f,
            0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
            0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
            -0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
            -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f
    };

    // vao
    unsigned int vao;
    glGenVertexArrays(1, &vao);
    glBindVertexArray(vao);

    // vbo
    unsigned int vbo;
    glGenBuffers(1, &vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vert), vert, GL_STATIC_DRAW);

    // pos
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void *) nullptr);
    glEnableVertexAttribArray(0);
    // normal
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void *) (3 * sizeof(float)));
    glEnableVertexAttribArray(1);
    // texCoords
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void *) (6 * sizeof(float)));
    glEnableVertexAttribArray(2);

    // textures and material
    int w, h, ch;
    unsigned int tex_diffuse;
    glGenTextures(1, &tex_diffuse);
    glBindTexture(GL_TEXTURE_2D, tex_diffuse);
    // set texture wrapping parameters
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    // set texture filtering parameters
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    // load texture
    auto stb_deleter = [](unsigned char *data) { stbi_image_free(data); };
    // diffuse texture
    kgl::Scope<unsigned char, decltype(stb_deleter)> image_diffuse(
            stbi_load("../kgl-data/texture/brickwall.jpg", &w, &h, &ch, 0), stb_deleter);
    if (nullptr != image_diffuse) {
        if (ch == 3)
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB, GL_UNSIGNED_BYTE, image_diffuse.get());
        else if (ch == 4)
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, image_diffuse.get());
        else if (ch == 1)
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, w, h, 0, GL_RED, GL_UNSIGNED_BYTE, image_diffuse.get());
        // gen mipmap
        glGenerateMipmap(GL_TEXTURE_2D);
    } else {
        kgl::Log::ErrorCore("Failed to load diffuse texture");
    }
    shader->Use();
    shader->SetSampler2D("_material.diffuse", 0);
    // specular texture
    unsigned int tex_specular;
    glGenTextures(1, &tex_specular);
    glBindTexture(GL_TEXTURE_2D, tex_specular);
    // set texture wrapping parameters
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    // set texture filtering parameters
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    kgl::Scope<unsigned char, decltype(stb_deleter)> image_specular(
            stbi_load("../kgl-data/texture/brickwall_spec.png", &w, &h, &ch, 0), stb_deleter);
    if (nullptr != image_specular) {
        if (ch == 3)
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB, GL_UNSIGNED_BYTE, image_specular.get());
        else if (ch == 4)
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, image_specular.get());
        else if (ch == 1)
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, w, h, 0, GL_RED, GL_UNSIGNED_BYTE, image_specular.get());
        // gen mipmap
        glGenerateMipmap(GL_TEXTURE_2D);
    } else {
        kgl::Log::ErrorCore("Failed to load specular texture");
    }
    shader->SetSampler2D("_material.specular", 1);

    shader->SetInt("_material.shininess", 32);

    // light meta data
    // direct light
    shader->SetInt("_direct_light_nums", 1);
    int index = 0;
    std::string lightStr = "_direct_lights[" + std::to_string(index) + "]";
    shader->SetFloat(lightStr + ".intensity", 1.0);
    shader->SetRGB(lightStr + ".ambient", {0.1f, 0.1f, 0.1f});
    shader->SetRGB(lightStr + ".diffuse", {1.0f, 1.0f, 1.0f});
    shader->SetRGB(lightStr + ".specular", {1.0f, 1.0f, 1.0f});
    glm::vec3 direction(1.0f, 1.0f, 1.0f);
    shader->SetVector3(lightStr + ".direction", direction);
    // transform
    kgl::Transform trans1(glm::vec3(0, 0, -2.0f),
                          glm::vec3(glm::radians(45.0f), 0.0f, 0), glm::vec3(1.0f));

    auto trans_ca = kgl::MakeRef<kgl::Transform>(glm::vec3(0.0f),
                                                 glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(1.0f));

    // camera

    auto camera = new FPSCamera(trans_ca,
                                kgl::MakeRef<kgl::PerspectiveCamera>(
                                        *trans_ca, glm::radians(60.0f),
                                        (float) winSize.x / (float) winSize.y, 0.01f, 1000.0f));
    ScriptManager scriptManager;
    scriptManager.add_script(camera);

    scriptManager.Setup();

    float rotate_x = 0.0f;

    // render init
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    while (kgl::singleton<kgl::Application>::getInstance()->isOpen()) {

        scriptManager.Update();

        if (kgl::Input::IsKeyPressed(kgl::Key::Space)) {
            rotate_x += 0.1f;
        }

        trans1.rotation = glm::quat({glm::radians(rotate_x),
                                     glm::radians(1.5f * rotate_x),
                                     glm::radians(0.8f * rotate_x)});

        win->Clear();

        shader->Use();

        shader->SetMatrix4("_model", trans1.get_trans_mat());
        shader->SetMatrix4("_view", camera->get_camera()->get_view_mat());
        shader->SetMatrix4("_projection", camera->get_camera()->get_projection_mat());
        shader->SetVector3("_eye_pos", trans_ca->position);
        shader->SetVector3(lightStr + ".direction", direction);

        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, tex_diffuse);
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, tex_specular);

        glBindVertexArray(vao);
        glDrawArrays(GL_TRIANGLES, 0, 36);

        win->Display();
    }

    scriptManager.Destroy();

    return 0;
}
